package org.beanutopia;

import static org.beanutopia.function.CompositeFunction.self;
import static org.beanutopia.function.PropertyFunctions.*;
import com.google.common.base.Predicate;
import static com.google.common.base.Predicates.not;
import static com.google.common.base.ReferenceType.STRONG;
import static com.google.common.base.ReferenceType.WEAK;
import com.google.common.collect.ClassToInstanceMap;
import static com.google.common.collect.Iterators.addAll;
import static com.google.common.collect.Iterators.filter;
import com.google.common.collect.Maps;
import static com.google.common.collect.Maps.newClassToInstanceMap;
import com.google.common.collect.ReferenceMap;
import com.google.common.collect.Sets;
import static com.google.common.collect.Sets.immutableSet;

import java.lang.annotation.Annotation;
import static java.util.Arrays.asList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;

/**
 * @author ymeymann
 * @since Apr 1, 2007 2:42:45 PM
 */
public class PropertyUtils {

  private static final ReferenceMap<Class<?>, Set<AbstractProperty<?,?>>> props = new ReferenceMap<Class<?>, Set<AbstractProperty<?,?>>>(WEAK, STRONG);
  private static final ClassToInstanceMap<AbstractProperty<?,?>> classToProp =
          newClassToInstanceMap(Maps.<Class<? extends AbstractProperty<?,?>>,AbstractProperty<?,?>>newConcurrentHashMap());

  static <B> void addProperty(AbstractProperty<? super B,?> property, Class<B> cl) {
    classToProp.put(property.id(), property);
    Set<AbstractProperty<?,?>> set = props.putIfAbsent(cl, Sets.<AbstractProperty<?,?>>newConcurrentHashSet(property));
    if (set != null) set.add(property);
  }

  public static <B,V> AbstractProperty<B,V> resolve(Class<? extends AbstractProperty<B,V>> cl) {
    return classToProp.getInstance(cl);
  }

  private static Comparator<Class<?>> classComparator = new Comparator<Class<?>>() {
    public int compare(Class<?> o1, Class<?> o2) {
      if (o1.isAssignableFrom(o2))
        return o1 == o2 ? 0 : 1;
      else 
        return -1;
    }
  };

  /**
   * @return -1 if cl2 is instance of cl1, 0 if cl1 == cl2, and 1 otherwise
   */
  public static Comparator<Class<?>> classComparator() { return classComparator; }

  private static final Predicate<Annotation> hidden =
          self(Annotation.class).
          reflect("annotationType", null).
          condition(isInstanceOf(Hidden.class));
  private static final Predicate<AbstractProperty> notHidden =
          self(AbstractProperty.class).
          reflect("annotations", null).
          cast(Annotation[].class).
                  compose(toIterable(Annotation.class)).
          condition(not(exists(hidden)));

  public static Set<AbstractProperty<?,?>> propertiesOf(Class<?> cl) {
    if (cl == null) return null;
    HashSet<AbstractProperty<?,?>> res = new HashSet<AbstractProperty<?,?>>();
    TreeSet<Class<?>> allClasses = new TreeSet<Class<?>>(classComparator());
    Class<?> iClass = cl;
    while (iClass != null) {
      allClasses.add(iClass);
      allClasses.addAll(asList(iClass.getInterfaces()));
      iClass = iClass.getSuperclass();
    }
    for (Class<?> clazz : allClasses) {
      Set<AbstractProperty<?, ?>> myProps = props.get(clazz);
      if (myProps != null) {
        addAll(res, filter(myProps.iterator(), notHidden));
      }
    }
    return immutableSet(res);
  }

  public static <B> void populate(B object, UpdatableProperty<? super B, ?>.Value... args) {
    for (UpdatableProperty<? super B, ?>.Value t : args) {
      t.apply(object);
    }
  }

}
